//cSMWObject.cpp
#include "cSMWObject.h"

/* There are only 5 sets of tileset-specific object sets. They can be
 * recognized by the presence of a specific object:
 * 0: Ice Blue vertical pipe
 * 1: Rock wall background
 * 2: Log bridge
 * 3: Multiple canvasses (from Bowser's Castle)
 * 4: Thin upward spikes (from ghost house)
 * This table maps each tileset to its matching object set. (TODO: there
 * must be a copy of this in ROM)
 */
const u8 SMWObject::ObjectSet[NUM_TILESETS] = {
	0, 1, 2, 3, 4, 4, 2, 0, 2, 3, 3, 3, 0, 4, 3};

/* For objects with no special decoding, this maps the object IDs to the tile #
 * they decode to. for objects with special decoding (i.e. they do not simply
 * fill their area with the same tile), this is ignored; I've set them to 0xFFF
 * so they stand out, but note that a hack could use that tile.
 * This array defines standard objects only.
 */
const u16 SMWObject::StdObjTiles[0x22] = {					//Object #s
	0xFFF, 0x002, 0x021, 0x023, 0x02A, 0x02B, 0x03F, 0x003, /* 00 - 07 */
	0x113, 0x11E, 0x124, 0x12E, 0x12F, 0x130, 0x132, 0xFFF, /* 08 - 0F */
	0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0x02C, 0xFFF, /* 10 - 17 */
	0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, /* 18 - 1F */
	0xFFF, 0xFFF /* 20 - 21 */
};

/* This array maps object IDs to tile #s for each object set.
 * Note that in some sets, not all objects are defined.
 */
const u16 SMWObject::TilesetObjTiles[5][16] = {
	   { //Set 0 - Ice Blue Vertical Pipe
		0xFFF, 0x165, 0x16C, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, /* 30 - 37 */
		0x16D, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF  /* 38 - 3F */
	}, { //Set 1 - Rock Wall Background
		0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, /* 30 - 37 */
		0xFFF, 0x16C, 0x16D, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF  /* 38 - 3F */
	}, { //Set 2 - Log Bridge
		0xFFF, 0xFFF, 0xFFF, 0x16C, 0x16D, 0xFFF, 0xFFF, 0xFFF, /* 30 - 37 */
		0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF  /* 38 - 3F */
	}, { //Set 3 - Multiple Canvasses
		0xFFF, 0xFFF, 0xFFF, 0xFFF, 0x16C, 0x16D, 0xFFF, 0xFFF, /* 30 - 37 */
		0xFFF, 0xFFF, 0xFFF, 0x1FF, 0xFFF, 0xFFF, 0xFFF, 0x165  /* 38 - 3F */
	}, { //Set 4 - Thin Upward Spikes
		0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0x15E, 0xFFF, /* 30 - 37 */
		0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF  /* 38 - 3F */
}};

/* I don't think this table is found in the ROM, but it specifies the tile
 * numbers for single-tile extended objects 0x4B to 0x97.
 */
const u16 SMWObject::ExtObjMap16_2[0x4D] = {
	0x107, 0x108, 0, 0, 0, 0, 0x76, 0x77, 0x78, 0x79, 0, 0, 0x73, //4B-57
	0x74, 0x75, 0x76, 0x93, 0x94, 0x95, 0x96, 0, 0x1FE, 0, 0, 0, 0, 0, //58-65
	0, 0, 0x91, 0x92, 0x96, 0x97, 0x9A, 0x9B, 0x9F, 0xA0, 0, 0, 0, 0, //66-73
	0, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0, 0, 0, 0, 0, 0, 0, //74-82
	0, 0, 0, 0, 0x16A, 0xC1, 0xC2, 0, 0, 0, 0, 0x16B, 0, 0, 0, 0, 0, //83-93
	0, 0, 0, 0x110}; //94-97

//Map16 tiles drawn by first 0x32 extended objects. Some ignore this table.
u16 SMWObject::ExtObjMap16[0x32];

//Whether these static values have been initialized.
bool SMWObject::StaticInitialized = false;

/* Default constructor.
 */
SMWObject::SMWObject()
{
	memset(&this->Raw, 0, sizeof(this->Raw));
	this->XPos = 0;
	this->YPos = 0;
	this->Width = 0;
	this->Height = 0;
	this->MinWidth = 0;
	this->MaxWidth = 0;
	this->MinHeight = 0;
	this->MaxHeight = 0;
	this->Selected = false;
	this->Type = OBJ_NORMAL;
	this->ObjID = 0;
	this->ScreenExitDest = 0;
	this->NumBytes = 0;
}

/* Destructor.
 */
SMWObject::~SMWObject()
{
}

/* Reads an object from a file.
 * Inputs:
 * -Level: Level the block is being loaded into. (Used to determine positioning
 *  from level mode.)
 * -File: File to read from.
 * -Screen: Screen number the object is placed on.
 * Returns: true on success, false on failure.
 * Notes:
 * -The screen number is updated if this is a screen jump object or the new
 *  screen flag is set.
 * -Returns false if the file could not be read or if it is the last object.
 */
bool SMWObject::Read(Level *Lv, ROMFile *File, u32 *Screen)
{
	//Initialize
	if(!SMWObject::StaticInitialized)
	{
		u8 IDLow[0x32];
		File->ReadNoSeek(IDLow, sizeof(IDLow), 0x6A748);
		for(u32 i=0; i<0x32; i++) SMWObject::ExtObjMap16[i] = IDLow[i];
		
		SMWObject::StaticInitialized = true;
	}
	
	if(!File->Read(this->Raw.Byte, 3) || (this->Raw.Byte[0] == 0xFF))
		return false;
	this->NumBytes = 3;
	
	//TODO: fix compiler struct padding, then this won't be necessary
	this->Raw.NewScreen =	BITS(this->Raw.Byte[0], 7, 7);
	this->Raw.ObjIDHigh =	BITS(this->Raw.Byte[0], 5, 6);
	this->Raw.YPos =		BITS(this->Raw.Byte[0], 0, 4);
	this->Raw.ObjIDLow =	BITS(this->Raw.Byte[1], 4, 7);
	this->Raw.XPos =		BITS(this->Raw.Byte[1], 0, 3);
	this->Raw.Settings =	this->Raw.Byte[2];
	
	if(this->Raw.NewScreen) (*Screen)++;
	this->ScreenExitDest = 0;
	this->ObjID = (this->Raw.ObjIDHigh << 4) | this->Raw.ObjIDLow;
	
	//Determine block type
	if(this->ObjID == 0)
	{
		this->ObjID = this->Raw.Settings;
		if(this->ObjID == 0)
		{
			this->Type = OBJ_SCREEN_EXIT;
			
			//Screen exit command has 4 bytes
			this->Raw.Byte[3] = File->Read();
			this->Raw.Dest = this->Raw.Byte[3];
			this->NumBytes++;
		}
		else if(this->ObjID == 1)
		{
			this->Type = OBJ_SCREEN_JUMP;
			(*Screen) = this->Raw.YPos;
		}
		else this->Type = OBJ_EXTENDED;
	}
	else this->Type = OBJ_NORMAL;
	
	//todo: position for vertical levels, using *Lv info
	this->XPos = ((*Screen) * 16) + this->Raw.XPos;
	this->YPos = this->Raw.YPos;
	
	//These values will not be valid until CalculateSize() is called. It uses
	//them to determine the real dimensions.
	this->Width = (this->Raw.Settings & 0xF) + 1;
	this->Height = (this->Raw.Settings >> 4) + 1;
	
	//Determine size
	this->CalculateSize();
	
	//DebugOut(DO_ALL, "T=%u B=%04X X=%u Y=%u W=%u H=%u\n",
	//	this->Type, this->ObjID, this->XPos, this->YPos, this->Width,
	//	this->Height);
	return true;
}

/* Calculates the current, minimum and maximum dimensions for this object.
 * TODO: Update function name to reflect that it handles SubType as well.
 */
void SMWObject::CalculateSize()
{
	if(this->Type != OBJ_NORMAL)
	{
		//todo: some extended objects such as Yoshi's House have other sizes
		this->MinWidth = 1;
		this->MinHeight = 1;
		this->MaxWidth = 1;
		this->MaxHeight = 1;
		this->Width = 1;
		this->Height = 1;
		this->SubType = 0;
		this->HasSubType = false;
		return;
	}
	
	//Default min/max
	this->MinHeight = 1;
	this->MaxHeight = 16;
	this->MinWidth = 1;
	this->MaxWidth = 16;
	this->SubType = 0;
	this->HasSubType = false;
	
	switch(this->ObjID)
	{
		case 0xF: //Horizontal pipe
			this->MinWidth = 2;
			this->MaxWidth = 2;
			this->Width = 2;
			this->SubType = LOWPART(this->Raw.Settings); //todo: this doesn't seem to be correct
			this->HasSubType = true;
		break;
		
		case 0x10: //Vertical pipe
			this->MinHeight = 2;
			this->MaxHeight = 2;
			this->Height = 2;
			this->SubType = HIPART(this->Raw.Settings);
			this->HasSubType = true;
		break;
		
		case 0x11: //Cannon
			this->MinWidth = 1;
			this->MaxWidth = 1;
			this->Width = 1;
		break;
		
		case 0x12: //Slope
			this->SubType = LOWPART(this->Raw.Settings);
			this->HasSubType = true;
			switch(this->SubType)
			{
				case 0: //Left normal
				case 3: //Right normal
				case 6: //Left normal upside-down
				case 7: //Right normal upside-down
					this->MinWidth = 2;
					this->MaxWidth = 32;
					this->Width = this->Height * 2;
					this->MinHeight = 2;
					this->MaxHeight = 17;
					this->Height++;
				break;
				
				case 1: //Left steep
				case 4: //Right steep
				case 8: //Left steep upside-down
				case 9: //Right steep upside-down
					this->MinHeight = 2;
					this->MaxHeight = 17;
					this->Height++;
				break;
				
				case 2: //Left gradual
				case 5: //Right gradual
					this->MinHeight = 2;
					this->MaxHeight = 17;
					this->Height++;
					this->MinWidth = 1;
					this->MaxWidth = 64;
					this->Width *= 4;
				break;
				
				default: break;
			}
		break;
		
		case 0x13: //Ledge egde
			this->MinWidth = 1;
			this->MaxWidth = 1;
			this->Width = 1;
			this->SubType = LOWPART(this->Raw.Settings);
			this->HasSubType = true;
			if(this->SubType == 7)
			{
				//DebugOut(DO_ALL, "(%u, %u) %02X %02X %02X %02X\n", this->XPos,
				//	this->YPos, this->Raw.Byte[0], this->Raw.Byte[1],
				//	this->Raw.Byte[2], this->Raw.Byte[3]);
				this->MinHeight = 2;
				this->MaxHeight = 17;
				this->Height++;
			}
		break;
		
		case 0x15: //Midway/goal point
			this->MinWidth = 3;
			this->MaxWidth = 3;
			this->Width = 3;
			this->Height--;
			this->SubType = HIPART(this->Raw.Settings) & 1;
			this->HasSubType = true;
		break;
		
		case 0x17: //Rope/clouds
			this->MinHeight = 1;
			this->MaxHeight = 1;
			this->Height = 1;
			this->SubType = ~LOWPART(this->Raw.Settings) & 1; //TODO: is this right? it looks right but it's odd
			this->HasSubType = true;
		break;
		
		case 0x1C: //Donut bridge
			this->MinHeight = 2;
			this->MaxHeight = 2;
			this->Height = 2;
		break;
		
		case 0x1E: //Net vertical edge
			this->MinWidth = 1;
			this->MaxWidth = 1;
			this->Width = 1;
			this->SubType = HIPART(this->Raw.Settings) & 1;
			this->HasSubType = true;
		break;
		
		case 0x1F: //Vertical pipe/bone/log
			this->MinWidth = 1;
			this->MaxWidth = 1;
			this->Width = 1;
		break;
		
		case 0x20: //Horizontal pipe/bone/log
			this->MinHeight = 1;
			this->MaxHeight = 1;
			this->Height = 1;
		break;
		
		case 0x21: //Long ground
			this->MinHeight = 3;
			this->MaxHeight = 3;
			this->Height = 3;
			this->Width = this->Raw.Settings + 1;
		break;
		
		default: break;
	}
}

/* Decodes the object into a level's Map16 grid.
 * Inputs:
 * -Lv: Level to decode into.
 * -Layer2: If true, decode into layer 2. (TODO)
 * Returns: true on success, false on failure.
 */
bool SMWObject::Decode(Level *Lv, bool Layer2)
{
	s32 X1, Y1, X2, Y2, Tile = 0xAAAA, Dst;
	SNESPointer Ptr;
	
	//Control objects decode to nothing
	if(!this->IsVisible()) return true;
	
	X1 = this->XPos;
	X2 = this->XPos + this->Width;
	Y1 = this->YPos;
	Y2 = this->YPos + this->Height;
	
	if(this->Type == OBJ_EXTENDED)
	{
		switch(this->ObjID)
		{
			case 0x41: //Dragon coin
				Lv->SetTileAt(X1, Y1, Lv->File->Read(0x6B528));
				Lv->SetTileAt(X1, Y1+1, Lv->File->Read(0x6B532));
				this->Height = 2;
			break;
			
			case 0x42: //Top left slope
				Lv->SetTileAt(X1, Y1, 0x1D8);
				Lv->SetTileAt(X1+1, Y1, 0x1DA);
			break;
			
			case 0x43: //Top right slope
				Lv->SetTileAt(X1, Y1, 0x1DB);
				Lv->SetTileAt(X1+1, Y1, 0x1DC);
			break;
			
			case 0x44: //Left purple triangle
				Lv->SetTileAt(X1, Y1, 0x1B4);
				Lv->SetTileAt(X1, Y1+1, 0x1EB);
			break;
			
			case 0x45: //Right purple triangle
				Lv->SetTileAt(X1, Y1, 0x1B5);
				Lv->SetTileAt(X1, Y1+1, 0x1EB);
			break;
			
			case 0x46: //Midway point bar
				Lv->SetTileAt(X1-1, Y1, 0x35);
				Lv->SetTileAt(X1, Y1, 0x38);
			break;
			
			case 0x47: //Door
				Lv->SetTileAt(X1, Y1, 0x1F);
				Lv->SetTileAt(X1, Y1+1, 0x20);
			break;
			
			case 0x48: //P-Switch door
				Lv->SetTileAt(X1, Y1, 0x27);
				Lv->SetTileAt(X1, Y1+1, 0x28);
			break;
			
			case 0x49: //Ghost house exit
				Ptr.Read(Lv->File, 0x6ECCB);
				this->Width = Lv->File->Read(0x6ECC4) + 1;
				this->Height = (Lv->File->Read(0x6ECDA) / this->Width);
				//for some reason this object is stored as # tiles instead of
				//height in the code
				
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
						Lv->SetTileAt(X1+X, Y1+Y, Lv->File->Read());
				}
			break;
			
			case 0x4A: //Net door
				Ptr.Read(Lv->File, 0x6A9CD);
				this->Width = Lv->File->Read(0x6A9C9) + 1;
				this->Height = (Lv->File->Read(0x6A9DF) / this->Width);
				
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
						Lv->SetTileAt(X1+X, Y1+Y, Lv->File->Read());
				}
			break;
			
			case 0x4D: //Line, large circle, top left
				Lv->SetTileAt(X1, Y1, 0x7A);
				Lv->SetTileAt(X1+1, Y1, 0x7B);
				Lv->SetTileAt(X1, Y1+1, 0x7C);
			break;
			
			case 0x4E: //Line, large circle, top right
				Lv->SetTileAt(X1, Y1, 0x7E);
				Lv->SetTileAt(X1+1, Y1, 0x7F);
				Lv->SetTileAt(X1+1, Y1+1, 0x7D);
			break;
			
			case 0x4F: //Line, large circle, bottom left
				Lv->SetTileAt(X1, Y1, 0x82);
				Lv->SetTileAt(X1, Y1+1, 0x80);
				Lv->SetTileAt(X1+1, Y1+1, 0x81);
			break;
			
			case 0x50: //Line, large circle, bottom right
				Lv->SetTileAt(X1+1, Y1, 0x83);
				Lv->SetTileAt(X1, Y1+1, 0x84);
				Lv->SetTileAt(X1+1, Y1+1, 0x85);
			break;
			
			case 0x55: //Horizontal line end
				Lv->SetTileAt(X1, Y1, 0x96);
				Lv->SetTileAt(X1, Y1+1, 0x97);
			break;
			
			case 0x56: //Vertical line end
				Lv->SetTileAt(X1, Y1, 0x98);
				Lv->SetTileAt(X1+1, Y1, 0x99);
			break;
			
			case 0x5F: //Large background area
				//TODO: floods a bunch of Map16 with 0x77; looks as if it has
				//some huge length and wraps at screen boundaries
			break;
			
			case 0x61: //Ghost house clock
				Lv->SetTileAt(X1, Y1, 0x97);
				Lv->SetTileAt(X1+1, Y1, 0x98);
				Lv->SetTileAt(X1+2, Y1, 0x99);
				Lv->SetTileAt(X1, Y1+1, 0x9A);
				Lv->SetTileAt(X1+1, Y1+1, 0x9B);
				Lv->SetTileAt(X1+2, Y1+1, 0x9C);
				Lv->SetTileAt(X1, Y1+2, 0x9D);
				Lv->SetTileAt(X1+1, Y1+2, 0x9E);
				Lv->SetTileAt(X1+2, Y1+2, 0x9F);
			break;
			
			case 0x62: //Ghost house top left to bottom right beam 1
				Lv->SetTileAt(X1, Y1, 0x86);
				Lv->SetTileAt(X1+1, Y1, 0x87);
				Lv->SetTileAt(X1, Y1+1, 0x86);
				Lv->SetTileAt(X1+2, Y1+1, 0x87);
				Lv->SetTileAt(X1+2, Y1+2, 0x86);
			break;
			
			case 0x63: //Ghost house top right to bottom left beam 1
				Lv->SetTileAt(X1+1, Y1, 0x84);
				Lv->SetTileAt(X1+2, Y1, 0x85);
				Lv->SetTileAt(X1, Y1+1, 0x84);
				Lv->SetTileAt(X1+1, Y1+1, 0x85);
				Lv->SetTileAt(X1, Y1+2, 0x85);
			break;
			
			case 0x64: //Ghost house top right cobweb
			case 0x65: //Ghost house top left cobweb
				//Todo: what happens if $6EBF8 != $6EC16? My guess is it would
				//use the latter for the first row/column, and then the former
				//afterward.
				//Also by default this object writes tile 0x25, so we need to
				//be able to tell that yes, that tile is part of the object
				//(i.e. we can't assume 0x25 is never part of an object).
				Ptr.Read(Lv->File, 0x6EC04);
				this->Width = Lv->File->Read(0x6EBF8) + 1;
				this->Height = Lv->File->Read(0x6EBF8) + 1; //same address
				
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
						Lv->SetTileAt(X1+X, Y1+Y, Lv->File->Read());
				}
			break;
			break;
			
			case 0x66: //Ghost house top left to bottom right beam 2
			case 0x67: //Ghost house top right to bottom left beam 2
				Ptr.Read(Lv->File, 0x6EC57);
				this->Width = Lv->File->Read(0x6EC4B) + 1;
				this->Height = Lv->File->Read(0x6EC4B) + 1; //same address
				
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
						Lv->SetTileAt(X1+X, Y1+Y, Lv->File->Read());
				}
			break;
			
			case 0x70: //Bit of canvass 1
				Lv->SetTileAt(X1, Y1, 0x84);
				Lv->SetTileAt(X1+1, Y1, 0x85);
			break;
			
			case 0x71: //Canvass 1
			case 0x72: //Canvass 2
			case 0x73: //Canvass 3
			case 0x74: //Canvass 4
				//TODO
			break;
			
			case 0x7C: //Bit of canvass 2
				Lv->SetTileAt(X1, Y1, 0x81);
				Lv->SetTileAt(X1, Y1+1, 0x84);
			break;
			
			case 0x7D: //Bit of canvass 3
				Lv->SetTileAt(X1, Y1, 0x82);
				Lv->SetTileAt(X1, Y1+1, 0x85);
			break;
			
			case 0x7E: //Bit of canvass 4
				Lv->SetTileAt(X1, Y1, 0x83);
				Lv->SetTileAt(X1, Y1+1, 0x86);
			break;
			
			case 0x7F: //Torpedo launcher
				Lv->SetTileAt(X1, Y1, 0x166);
				Lv->SetTileAt(X1+1, Y1, 0x167);
				Lv->SetTileAt(X1, Y1+1, 0x168);
				Lv->SetTileAt(X1+1, Y1+1, 0x169);
			break;
			
			case 0x80: //Ghost house entrance
				Ptr.Read(Lv->File, 0x6ED76);
				this->Width = Lv->File->Read(0x6ED6F) + 1;
				this->Height = (Lv->File->Read(0x6ED8F) / this->Width);
				
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
						Lv->SetTileAt(X1+X, Y1+Y, Lv->File->Read());
				}
			break;
			
			case 0x81: //Water weed
				Lv->SetTileAt(X1, Y1, 0xC9);
				Lv->SetTileAt(X1, Y1+1, 0xCA);
			break;
			
			case 0x82: //Big bush 1
			case 0x83: //Big bush 2
				if(this->ObjID == 0x82)
				{
					Ptr.Read(Lv->File, 0x6A932);
					this->Width = Lv->File->Read(0x6A91E) + 1;
					this->Height = Lv->File->Read(0x6A922) + 1;
				}
				else
				{
					Ptr.Read(Lv->File, 0x6A977);
					this->Width = Lv->File->Read(0x6A963) + 1;
					this->Height = Lv->File->Read(0x6A967) + 1;
				}
				
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
					{
						Tile = Lv->File->Read();
						Dst = Lv->GetTileAt(X1+X, Y1+Y);
						
						//decompiled from 0D:A78D
						if(Tile == 0x25) continue;
						else if((Tile >= 0x54) && (Dst != 0x25))
						{
							if(Dst != 0x49) Tile++;
							Tile++;
						}
						Lv->SetTileAt(X1+X, Y1+Y, Tile);
					}
				}
			break;
			
			case 0x84: //Castle entrance
				//TODO
			break;
			
			case 0x85: //Yoshi's house
				Ptr.Read(Lv->File, 0x6EE3F); //todo: what if the 2 pointers are different?
				this->Width = Lv->File->Read(0x6EE38) + 1;
				this->Height = (Lv->File->Read(0x6EE58) / this->Width);
				
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
						Lv->SetTileAt(X1+X, Y1+Y, Lv->File->Read());
				}
			break;
			
			case 0x86: //Goal arrow sign
				Ptr.Read(Lv->File, 0x6A9F2);
				this->Width = 2; //the game does AND #$01 to set the width
				this->Height = (Lv->File->Read(0x6AA05) / this->Width);
				
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
						Lv->SetTileAt(X1+X, Y1+Y, Lv->File->Read());
				}
			break;
			
			case 0x8A: //Green switch palace switch
			case 0x8B: //Yellow switch palace switch
			case 0x8C: //Blue switch palace switch
			case 0x8D: //Red switch palace switch
				Ptr.Read(Lv->File, 0x6EEAD);
				this->Width = Lv->File->Read(0x6EEA1) + 1;
				this->Height = Lv->File->Read(0x6EEA1) + 1; //both same address
				
				Ptr += (this->ObjID - 0x8A) * (this->Width * this->Height);
				Lv->File->Seek(&Ptr);
				for(u32 Y=0; Y<this->Height; Y++)
				{
					for(u32 X=0; X<this->Width; X++)
						Lv->SetTileAt(X1+X, Y1+Y, Lv->File->Read());
				}
			break;
			
			case 0x8F: //Ghost house window
				Lv->SetTileAt(X1, Y1, 0xFC);
				Lv->SetTileAt(X1+1, Y1, 0xFD);
				Lv->SetTileAt(X1, Y1+1, 0xFE);
				Lv->SetTileAt(X1+1, Y1+1, 0xFF);
			break;
			
			case 0x90: //Boss door
				Lv->SetTileAt(X1, Y1, 0x98);
				Lv->SetTileAt(X1+1, Y1, 0x99);
				Lv->SetTileAt(X1, Y1+1, 0x9A);
				Lv->SetTileAt(X1+1, Y1+1, 0x9B);
				Lv->SetTileAt(X1, Y1+2, 0x9C);
				Lv->SetTileAt(X1+1, Y1+2, 0x9C);
			break;
			
			case 0x91: //Steep left slope for vertical level
				Lv->SetTileAt(X1, Y1, 0x1AA);
				Lv->SetTileAt(X1, Y1+1, 0x1E2);
			break;
			
			case 0x92: //Steep right slope for vertical level
				Lv->SetTileAt(X1, Y1, 0x1AF);
				Lv->SetTileAt(X1, Y1+1, 0x1E4);
			break;
			
			case 0x93: //Left slope for vertical level
				Lv->SetTileAt(X1, Y1, 0x196);
				Lv->SetTileAt(X1+1, Y1, 0x19B);
				Lv->SetTileAt(X1, Y1+1, 0x1DE);
				Lv->SetTileAt(X1+1, Y1+1, 0x1E6);
			break;
			
			case 0x94: //Right slope for vertical level
				Lv->SetTileAt(X1, Y1, 0x1A0);
				Lv->SetTileAt(X1+1, Y1, 0x1A5);
				Lv->SetTileAt(X1, Y1+1, 0x1E6);
				Lv->SetTileAt(X1+1, Y1+1, 0x1E0);
			break;
			
			case 0x95: //Very steep left slope for vertical level
				Lv->SetTileAt(X1, Y1, 0x1CA);
				Lv->SetTileAt(X1, Y1+1, 0x1CB);
				Lv->SetTileAt(X1, Y1+2, 0x1F1);
			break;
			
			case 0x96: //Very steep right slope for vertical level
				Lv->SetTileAt(X1, Y1, 0x1CC);
				Lv->SetTileAt(X1, Y1+1, 0x1CD);
				Lv->SetTileAt(X1, Y1+2, 0x1F2);
			break;
			
			default:
				if(this->ObjID < 0x41)
					Lv->SetTileAt(X1, Y1,
						SMWObject::ExtObjMap16[this->ObjID - 0x10]);
				else Lv->SetTileAt(X1, Y1,
						SMWObject::ExtObjMap16_2[this->ObjID - 0x4B]);
			break;
			
			this->MinHeight = this->Height;
			this->MinWidth = this->Width;
			this->MaxHeight = this->Height;
			this->MaxWidth = this->Width;
		}
		return true;
	}
	
	//todo: vertical level support. Can we just decode as normal, and read the
	//array differently in the Level class?
	//also can we move some of this to separate files? it's getting huge.
	//(that's what she said)
	//no, no we can't because C++ is a steaming pile of shit. nevermind.
	//enjoy this ridiculously fucking huge function courtesy of C++ failness.
	switch(this->ObjID)
	{
		case 0xF: //Vertical pipe
			if(this->SubType == 2) Y2 = this->YPos + (this->Height - 1);
			
			//Fill with pipe tile
			for(s32 Y = Y1; Y < Y2; Y++)
			{
				Lv->SetTileAt(X1, Y, this->SubType == 5 ? 0x168 : 0x135);
				Lv->SetTileAt(X1 + 1, Y, this->SubType == 5 ? 0x169 : 0x136);
			}
			
			switch(this->SubType)
			{
				case 0: //End on top
					Lv->SetTileAt(X1, Y1, 0x133);
					Lv->SetTileAt(X1 + 1, Y1, 0x134);
				break;
				
				case 1: //End on top, exit enabled
					Lv->SetTileAt(X1, Y1, 0x137);
					Lv->SetTileAt(X1 + 1, Y1, 0x138);
				break;
				
				case 2: //Double-ended
					Lv->SetTileAt(X1, Y1, 0x139);
					Lv->SetTileAt(X1 + 1, Y1, 0x13A);
					Lv->SetTileAt(X1, Y2, 0x139);
					Lv->SetTileAt(X1 + 1, Y2, 0x13A);
					//todo: there must be a flip setting? How does it use the
					//same tiles for top and bottom?
				break;
				
				case 3: //End on bottom
					Lv->SetTileAt(X1, Y2, 0x133);
					Lv->SetTileAt(X1 + 1, Y2, 0x134);
				break;
				
				case 4: //End on bottom, exit enabled
					Lv->SetTileAt(X1, Y2, 0x137);
					Lv->SetTileAt(X1 + 1, Y2, 0x138);
				break;
				
				default: break;
			}
		break;
		
		case 0x10: //Horizontal pipe
			//Fill with pipe tile
			for(s32 X = X1; X < X2; X++)
			{
				Lv->SetTileAt(X, Y1, 0x13D);
				Lv->SetTileAt(X, Y1 + 1, 0x13E);
			}
			
			switch(this->SubType)
			{
				case 0: //End on left
					Lv->SetTileAt(X1, Y1, 0x13B);
					Lv->SetTileAt(X1, Y1 + 1, 0x13C);
				break;
				
				case 1: //End on left, exit enabled
					Lv->SetTileAt(X1, Y1, 0x13B); //interesting
					Lv->SetTileAt(X1, Y1 + 1, 0x13F);
				break;
				
				case 2: //End on right
					Lv->SetTileAt(X2, Y1, 0x13B);
					Lv->SetTileAt(X2, Y1 + 1, 0x13C);
				break;
				
				case 3: //End on right, exit enabled
					Lv->SetTileAt(X2, Y1, 0x13B);
					Lv->SetTileAt(X2, Y1 + 1, 0x13F);
				break;
				
				default: break;
			}
		break;
		
		case 0x12: //Slope
			switch(this->SubType)
			{
				case 0: //Left normal
					for(s32 Y = Y1; Y < Y2; Y++)
					{
						if(Y < (Y2 - 1))
						{
							Lv->SetTileAt(X1, Y, 0x196);
							Lv->SetTileAt(X1 + 1, Y, 0x19B);
							Lv->SetTileAt(X1, Y + 1, 0x1DE);
							Lv->SetTileAt(X1 + 1, Y + 1, 0x1E6);
						}
						for(s32 X = X1 + 4; X < X2; X++)
							Lv->SetTileAt(X, Y, 0x3F);
						X1 -= 2;
					}
				break;
				
				case 1: //Left steep
					X1 = X2;
					for(s32 Y = Y1; Y < Y2-1; Y++)
					{
						Lv->SetTileAt(X1-2, Y, 0x1AA);
						Lv->SetTileAt(X1-2, Y+1, 0x1E2);
						for(s32 X = X1; X < X2-1; X++)
						{
							Lv->SetTileAt(X, Y, 0x3F);
							Lv->SetTileAt(X, Y+1, 0x3F);
						}
						X1--;
					}
					Lv->SetTileAt(X1, Y2-1, 0x3F);
				break;
				
				case 2: //Left gradual
					for(s32 Y = Y1; Y < Y2; Y++)
					{
						if(Y < (Y2 - 1))
						{
							Lv->SetTileAt(X1, Y, 0x16E);
							Lv->SetTileAt(X1, Y + 1, 0x1D8);
							Lv->SetTileAt(X1 + 1, Y, 0x173);
							Lv->SetTileAt(X1 + 1, Y + 1, 0x1DA);
							Lv->SetTileAt(X1 + 2, Y, 0x178);
							Lv->SetTileAt(X1 + 2, Y + 1, 0x1E6);
							Lv->SetTileAt(X1 + 3, Y, 0x17D);
							Lv->SetTileAt(X1 + 3, Y + 1, 0x1E6);
						}
						for(s32 X = X1 + 8; X < X2; X++)
							Lv->SetTileAt(X, Y, 0x3F);
						X1 -= 4;
					}
				break;
				
				case 3: //Right normal
					X2 = X1;
					for(s32 Y = Y1; Y < Y2; Y++)
					{
						for(s32 X = X1; X < X2; X++)
							Lv->SetTileAt(X, Y, 0x3F);
						if(Y < (Y2 - 1))
						{
							Lv->SetTileAt(X2, Y, 0x1A0);
							Lv->SetTileAt(X2 + 1, Y, 0x1A5);
							Lv->SetTileAt(X2, Y + 1, 0x1E6);
							Lv->SetTileAt(X2 + 1, Y + 1, 0x1E1);
						}
						X2 += 2;
					}
				break;
				
				case 4: //Right steep
					X2 = X1;
					for(s32 Y = Y1; Y < Y2-1; Y++)
					{
						Lv->SetTileAt(X2, Y, 0x1AF);
						Lv->SetTileAt(X2, Y+1, 0x1E4);
						for(s32 X = X1; X < X2; X++)
						{
							Lv->SetTileAt(X, Y+1, 0x3F);
							//Lv->SetTileAt(X, Y+2, 0xBB);
						}
						X2++;
					}
					Lv->SetTileAt(X1, Y2-1, 0x3F);
				break;
				
				case 5: //Right gradual
					X2 = X1;
					for(s32 Y = Y1; Y < Y2 - 1; Y++)
					{
						for(s32 X = X1; X < X2; X++)
							Lv->SetTileAt(X, Y+1, 0x3F);
						Lv->SetTileAt(X2, Y, 0x182);
						Lv->SetTileAt(X2, Y + 1, 0x1E6);
						Lv->SetTileAt(X2 + 1, Y, 0x187);
						Lv->SetTileAt(X2 + 1, Y + 1, 0x1E6);
						Lv->SetTileAt(X2 + 2, Y, 0x18C);
						Lv->SetTileAt(X2 + 2, Y + 1, 0x1DB);
						Lv->SetTileAt(X2 + 3, Y, 0x191);
						Lv->SetTileAt(X2 + 3, Y + 1, 0x1DC);
						X2 += 4;
					}
				break;
				
				case 6: //Left normal upside-down - TODO
				break;
				
				case 7: //Right normal upside-down
				break;
				
				case 8: //Left steep upside-down
				break;
				
				case 9: //Right steep upside-down
				break;
				
				default: break;
			}
		break;
		
		case 0x13: //Ground edge
			switch(this->SubType)
			{
				case 0: //Left
				case 1: //Right
					for(s32 Y=Y1; Y < Y2; Y++)
						Lv->SetTileAt(X1, Y, 0x40 + this->SubType);
				break;
				
				case 2: //Vine
					for(s32 Y=Y1; Y < Y2; Y++) Lv->SetTileAt(X1, Y, 6);
				break;
				
				case 3: //Top left, solid
					Lv->SetTileAt(X1, Y1, 0x145);
					for(s32 Y=Y1 + 1; Y < Y2; Y++) Lv->SetTileAt(X1, Y, 0x14B);
				break;
				
				case 4: //Left, solid
					for(s32 Y=Y1; Y < Y2; Y++) Lv->SetTileAt(X1, Y, 0x14B);
				break;
				
				case 5: //Top right, solid
					Lv->SetTileAt(X1, Y1, 0x148);
					for(s32 Y=Y1 + 1; Y < Y2; Y++) Lv->SetTileAt(X1, Y, 0x14C);
				break;
				
				case 6: //Right, solid
					for(s32 Y=Y1; Y < Y2; Y++) Lv->SetTileAt(X1, Y, 0x14C);
				break;
				
				case 7: //Top left, solid top
					switch(Lv->GetTileAt(X1, Y1))
					{
						case 0x1DE: Lv->SetTileAt(X1, Y1, 0x1DF); break;
						case 0x19B: Lv->SetTileAt(X1, Y1, 0x1BC); break;
						case 0x196: Lv->SetTileAt(X1, Y1, 0x102); break;
						default: Lv->SetTileAt(X1, Y1, 0x101); break;
					}
					for(s32 Y=Y1 + 1; Y < Y2-1; Y++) Lv->SetTileAt(X1, Y, 0x40);
				break;
				
				case 8: //Top right, solid top
					switch(Lv->GetTileAt(X1, Y1))
					{
						case 0x1E0: Lv->SetTileAt(X1, Y1, 0x1E1); break;
						case 0x1A0: Lv->SetTileAt(X1, Y1, 0x1BE); break;
						case 0x1A5: Lv->SetTileAt(X1, Y1, 0x104); break;
						default: Lv->SetTileAt(X1, Y1, 0x103); break;
					}
					for(s32 Y=Y1 + 1; Y < Y2; Y++) Lv->SetTileAt(X1, Y, 0x41);
				break;
				
				case 9: //Top left, solid with steep slope
					Lv->SetTileAt(X1, Y1, 0x1B6);
					for(s32 Y=Y1 + 1; Y < Y2; Y++) Lv->SetTileAt(X1, Y, 0x14B);
				break;
				
				case 0xA: //Top right, solid with steep slope
					Lv->SetTileAt(X1, Y1, 0x1B7);
					for(s32 Y=Y1 + 1; Y < Y2; Y++) Lv->SetTileAt(X1, Y, 0x14C);
				break;
				
				case 0xB: //Top+bottom left, solid
					switch(Lv->GetTileAt(X1, Y1))
					{
						case 0x1DE: Lv->SetTileAt(X1, Y1, 0x1DF); break;
						case 0x19B: Lv->SetTileAt(X1, Y1, 0x1BC); break;
						case 0x196: Lv->SetTileAt(X1, Y1, 0x146); break;
						default: Lv->SetTileAt(X1, Y1, 0x145); break;
					}
					for(s32 Y=Y1 + 1; Y < Y2; Y++)
						Lv->SetTileAt(X1, Y, 0x14B);
					Lv->SetTileAt(X1, Y2, 0x1E2);
				break;
				
				case 0xC: //Bottom left, solid
					for(s32 Y=Y1; Y < Y2 - 1; Y++) Lv->SetTileAt(X1, Y, 0x14B);
					Lv->SetTileAt(X1, Y2 - 1, 0x1E2);
				break;
				
				case 0xD: //Top+bottom right, solid
					switch(Lv->GetTileAt(X1, Y1))
					{
						case 0x1A0: Lv->SetTileAt(X1, Y1, 0x1BE); break;
						case 0x1A5: Lv->SetTileAt(X1, Y1, 0x149); break;
						case 0x1E0: Lv->SetTileAt(X1, Y1, 0x1E1); break;
						default: Lv->SetTileAt(X1, Y1, 0x148); break;
					}
					for(s32 Y=Y1 + 1; Y < Y2; Y++)
						Lv->SetTileAt(X1, Y, 0x14C);
					Lv->SetTileAt(X1, Y2, 0x1E4);
				break;
				
				case 0xE: //Bottom right, solid
					for(s32 Y=Y1; Y < Y2 - 1; Y++) Lv->SetTileAt(X1, Y, 0x14C);
					Lv->SetTileAt(X1, Y2 - 1, 0x1E4);
				break;
				
				default: break;
			}
		break;
		
		case 0x14: //Edge, top, solid
		case 0x21: //Ground, wide
			for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y1, 0x100);
			for(s32 Y=Y1 + 1; Y<Y2; Y++)
			{
				for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y, 0x3F);
			}
		break;
		
		case 0x15: //Midway point (0)/goal point (1)
			switch(this->SubType)
			{
				case 0: Tile = 0x2F; break;
				case 1: Tile = 0x39; break;
			}
			
			Lv->SetTileAt(X1, Y1, Tile);
			Lv->SetTileAt(X1+2, Y1, Tile + 3);
			for(s32 Y=Y1+1; Y<Y2; Y++)
			{
				Lv->SetTileAt(X1, Y, Tile + 1);
				Lv->SetTileAt(X1+2, Y, Tile + 4);
			}
			Lv->SetTileAt(X1, Y2, Tile + 2);
			Lv->SetTileAt(X1+2, Y2, Tile + 5);
		break;
		
		case 0x17: //Rope (0)/clouds (1)
			for(s32 Y = Y1; Y < Y2; Y++)
			{
				for(s32 X = X1; X < X2; X++)
					Lv->SetTileAt(X, Y, 0x105 + this->SubType);
			}
		break;
		
		case 0x18: //Water surface, animated
		case 0x19: //Water surface
			for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y1, this->ObjID - 0x18);
			for(s32 Y=Y1 + 1; Y<Y2; Y++)
			{
				for(s32 X=X1; X<X2; X++)
					Lv->SetTileAt(X, Y1, this->ObjID - 0x16);
			}
		break;
		
		case 0x1A: //Lava/Mud
			for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y1, 4);
			for(s32 Y=Y1 + 1; Y<Y2; Y++)
			{
				for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y1, 5);
			}
		break;
		
		case 0x1B: //Net, top edge
			for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y1, 8);
			for(s32 Y=Y1 + 1; Y<Y2; Y++)
			{
				for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y1, 0xB);
			}
		break;
		
		case 0x1C: //Bridge, donut
			for(s32 X=X1; X<X2; X++)
			{
				Lv->SetTileAt(X, Y1, 0x26);
				Lv->SetTileAt(X, Y1+1, 0x144);
			}
		break;
		
		case 0x1D: //Net, bottom edge
			for(s32 Y=Y1; Y<Y2-1; Y++)
			{
				for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y1, 0xB);
			}
			for(s32 X=X1; X<X2; X++) Lv->SetTileAt(X, Y2-1, 0xE);
		break;
		
		case 0x1E: //Net, left edge (0)/right edge (1)
			for(s32 Y=Y1; Y<Y2; Y++)
			{
				switch(Lv->GetTileAt(X1, Y))
				{
					case 8: Lv->SetTileAt(X1, Y, this->SubType ? 9 : 7); break;
					case 0xE: Lv->SetTileAt(X1, Y, this->SubType ? 0xF : 0xD);
					break;
					default: Lv->SetTileAt(X1, Y, this->SubType ? 0xC : 0xA);
					break;
				}
			}
		break;
		
		case 0x1F: //Pipe/Bone/Log, vertical, skinny
			Lv->SetTileAt(X1, Y1, 0x153);
			for(s32 Y=Y1+1; Y<Y2-1; Y++) Lv->SetTileAt(X1, Y, 0x154);
			Lv->SetTileAt(X1, Y2-1, 0x155);
		break;
		
		case 0x20: //Pipe/Bone/Log, horizontal, skinny
			Lv->SetTileAt(X1, Y1, 0x156);
			for(s32 X=X1+1; X<X2-1; X++) Lv->SetTileAt(X, Y1, 0x157);
			Lv->SetTileAt(X2-1, Y1, 0x158);
		break;
		
		default: //Same tile repeated for the entire area
			for(s32 Y = Y1; Y < Y2; Y++)
			{
				for(s32 X = X1; X < X2; X++)
				{
					if(this->ObjID < 0x22)
						Lv->SetTileAt(X, Y, SMWObject::StdObjTiles[this->ObjID]);
					else if((this->ObjID >= 0x30) && (this->ObjID < 0x40)
					&& (Lv->Header.Tileset < NUM_TILESETS))
					{
						Lv->SetTileAt(X, Y,
							SMWObject::TilesetObjTiles
								[SMWObject::ObjectSet[
									Lv->Header.Tileset]][this->ObjID - 0x30]);
					}
					else Lv->SetTileAt(X, Y, this->ObjID);
				}
			}
		break;
	}
	return true;
}

/* Determines whether an object should be rendered (i.e. is not a control
 * object).
 * Returns: true if visible, false if not.
 */
bool SMWObject::IsVisible()
{
	return ((this->Type != OBJ_SCREEN_EXIT) && (this->Type != OBJ_SCREEN_JUMP));
}

/* Determines whether the SubType property is valid for this object.
 * Returns: true if valid, false if not.
 */
bool SMWObject::GetHasSubType()
{
	return this->HasSubType;
}

/* Retrieves a 32-bit ID that uniquely identifies this object type. Since the
 * game uses the same internal ID for multiple objects, differentiating them by
 * the settings byte and/or the current tileset, this function combines the
 * internal ID, subtype, and tileset to uniquely identify an object type.
 * Inputs:
 * -Tileset: The tileset number the object will be rendered in.
 * Returns: Unique ID.
 */
u32 SMWObject::GetID(u8 Tileset)
{
	u32 TDL, ID = this->ObjID;
	if(this->Type == OBJ_EXTENDED) ID += 256;
	if((this->Type == OBJ_NORMAL) && (this->ObjID < 0x2E)) TDL = 0; //Same in every tileset
	else if(this->Type == OBJ_EXTENDED) TDL = 0; //AFAIK extended objects are also the same
	else if(Tileset >= NUM_TILESETS) TDL = 0;
	else TDL = SMWObject::ObjectSet[Tileset];
	return (TDL << 24) | (ID << 8) | this->SubType;
}
